/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package skyproc;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Objects;
import java.util.zip.DataFormatException;
import lev.LExporter;
import lev.LFlags;
import lev.LShrinkArray;
import lev.LStream;
import skyproc.exceptions.BadParameter;
import skyproc.exceptions.BadRecord;

/**
 *
 * @author Justin Swanson
 */
abstract class MagicItem extends MajorRecordDescription {

    SubData OBND = new SubData(Type.OBND);
    SubList<MagicEffectRef> magicEffects = new SubList<>(new MagicEffectRef());
    public KeywordSet keywords = new KeywordSet();

    MagicItem() {
	super();
    }

    MagicItem(Mod modToOriginateFrom, String edid) {
	super(modToOriginateFrom, edid);
    }

    void init() {
	subRecords.remove(Type.FULL);
	subRecords.remove(Type.DESC);
	OBND.initialize(12);
    }

    @Override
    ArrayList<FormID> allFormIDs() {
	ArrayList<FormID> out = super.allFormIDs();
	out.addAll(magicEffects.allFormIDs());
	return out;
    }

    static class SPIT extends SubRecord {

	int baseCost = 0;
	LFlags flags = new LFlags(4);
	int baseType = 0;
	float chargeTime = 0;
	CastType castType = CastType.ConstantEffect;
	DeliveryType targetType = DeliveryType.Self;
	float castDuration = 0;
	float range = 0;
	boolean valid = true;
	FormID perkType = new FormID();

	SPIT() {
	    super(Type.SPIT);
	    valid = false;
	}

	SPIT(LShrinkArray in) throws BadRecord, DataFormatException, BadParameter, IOException {
	    this();
	    parseData(in);
	}

	@Override
	SubRecord getNew(Type type) {
	    return new SPIT();
	}

	@Override
	final void parseData(LStream in) throws BadRecord, DataFormatException, BadParameter, IOException {
	    super.parseData(in);

	    baseCost = in.extractInt(4);
	    flags = new LFlags(in.extract(4));
	    baseType = in.extractInt(4);
	    chargeTime = in.extractFloat();
	    castType = CastType.values()[in.extractInt(4)];
	    targetType = DeliveryType.values()[in.extractInt(4)];
	    castDuration = in.extractFloat();
	    range = in.extractFloat();
	    perkType.setInternal(in.extract(4));

	    if (logging()) {
		logSync("", "SPIT record: ");
		logSync("", "  " + "Base Spell Cost: " + baseCost + ", flags: " + flags
			+ ", Base Type: " + baseType + ", Spell Charge Time: " + chargeTime);
		logSync("", "  " + "cast type: " + castType + ", targetType: " + targetType
			+ ", Cast Duration: " + castDuration
			+ ", Spell Range: " + range + ", Perk for Spell: " + perkType);
	    }

	    valid = true;
	}

	@Override
	void export(LExporter out, Mod srcMod) throws IOException {
	    super.export(out, srcMod);
	    if (isValid()) {
		out.write(baseCost);
		out.write(flags.export(), 4);
		out.write(baseType);
		out.write(chargeTime);
		out.write(castType.ordinal());
		out.write(targetType.ordinal());
		out.write(castDuration);
		out.write(range);
		perkType.export(out);
	    }
	}

	@Override
	Boolean isValid() {
	    return valid;
	}

	@Override
	int getContentLength(Mod srcMod) {
	    if (isValid()) {
		return 36;
	    } else {
		return 0;
	    }
	}

	@Override
	ArrayList<FormID> allFormIDs() {
	    ArrayList<FormID> out = new ArrayList<FormID>(1);
	    out.add(perkType);
	    return out;
	}
        
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final SPIT other = (SPIT) obj;
            if (this.baseCost != other.baseCost) {
                return false;
            }
            if (!Objects.equals(this.flags, other.flags)) {
                return false;
            }
            if (this.baseType != other.baseType) {
                return false;
            }
            if (Float.floatToIntBits(this.chargeTime) != Float.floatToIntBits(other.chargeTime)) {
                return false;
            }
            if (this.castType != other.castType) {
                return false;
            }
            if (this.targetType != other.targetType) {
                return false;
            }
            if (Float.floatToIntBits(this.castDuration) != Float.floatToIntBits(other.castDuration)) {
                return false;
            }
            if (Float.floatToIntBits(this.range) != Float.floatToIntBits(other.range)) {
                return false;
            }
            if (this.valid != other.valid) {
                return false;
            }
            if (!Objects.equals(this.perkType, other.perkType)) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 97 * hash + this.baseCost;
            hash = 97 * hash + Objects.hashCode(this.flags);
            hash = 97 * hash + this.baseType;
            hash = 97 * hash + Float.floatToIntBits(this.chargeTime);
            hash = 97 * hash + (this.castType != null ? this.castType.hashCode() : 0);
            hash = 97 * hash + (this.targetType != null ? this.targetType.hashCode() : 0);
            hash = 97 * hash + Float.floatToIntBits(this.castDuration);
            hash = 97 * hash + Float.floatToIntBits(this.range);
            hash = 97 * hash + (this.valid ? 1 : 0);
            hash = 97 * hash + Objects.hashCode(this.perkType);
            return hash;
        }

        @Override
        public SubRecord merge(SubRecord no, SubRecord bo) {
            super.merge(no, bo);
            SPIT s = this;
            if (!(no == null && bo == null && (no instanceof SPIT) && (bo instanceof SPIT))) {
                final SPIT nspit = (SPIT) no;
                final SPIT bspit = (SPIT) bo;
                SkyBash.merge(s.baseCost, nspit.baseCost, bspit.baseCost, type[0], "base cost");
                SkyBash.merge(s.baseType, nspit.baseType, bspit.baseType, type[0], "base type");
                SkyBash.merge(s.chargeTime, nspit.chargeTime, bspit.chargeTime, type[0], "charge time");
                SkyBash.merge(s.castType, nspit.castType, bspit.castType, type[0], "cast type");
                SkyBash.merge(s.targetType, nspit.targetType, bspit.targetType, type[0], "target type");
                SkyBash.merge(s.castDuration, nspit.castDuration, bspit.castDuration, type[0], "cast duration");
                SkyBash.merge(s.range, nspit.range, bspit.range, type[0], "cast duration");
                SkyBash.merge(s.valid, nspit.valid, bspit.valid, type[0], "valid");
                s.flags.merge(nspit.flags, bspit.flags, type[0]);
                s.perkType.merge(nspit.perkType, bspit.perkType, type[0]);
            }
            return s;
        }
    }

    public ArrayList<MagicEffectRef> getMagicEffects() {
	return magicEffects.toPublic();
    }

    public void removeMagicEffect(MagicEffectRef magicEffect) {
	magicEffects.remove(magicEffect);
    }

    public void addMagicEffect(MagicEffectRef magicEffect) {
	magicEffects.add(magicEffect);
    }

    public void addMagicEffect(MGEF magicEffect) {
	magicEffects.add(new MagicEffectRef(magicEffect.getForm()));
    }

    public void clearMagicEffects() {
	magicEffects.clear();
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final MagicItem other = (MagicItem) obj;
        if (!Objects.equals(this.OBND, other.OBND)) {
            return false;
        }
        if (!Objects.equals(this.magicEffects, other.magicEffects)) {
            return false;
        }
        if (!Objects.equals(this.keywords, other.keywords)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 13 * hash + Objects.hashCode(this.OBND);
        hash = 13 * hash + Objects.hashCode(this.magicEffects);
        hash = 13 * hash + Objects.hashCode(this.keywords);
        return hash;
    }

    @Override
    public MajorRecord merge(MajorRecord no, MajorRecord bo) {
        super.merge(no, bo);
        MagicItem m = this;
        if (!(no == null && bo == null && (no instanceof MagicItem) && (bo instanceof MagicItem))) {
            final MagicItem nmi = (MagicItem) no;
            final MagicItem bmi = (MagicItem) bo;
            m.OBND.merge(nmi.OBND, bmi.OBND);
            m.keywords.merge(nmi.keywords, bmi.keywords);
            m.magicEffects.merge(nmi.magicEffects, bmi.magicEffects);
        }
        return m;
    }
}
